﻿using System;

namespace SRS.Encryption
{
    /// <summary>
    /// Anchor Class of ECB, CBC, CFB and OFB. This class provide method's to be used by child class.
    /// </summary>
    public class SRSEncryption
    {
        public SRSEncryption()
        {
        }


        #region Encrypt
        /// <summary>
        /// Encrypt
        /// </summary>
        /// <param name="key">Encryption key</param>
        /// <param name="plainText">Plain text which are encrypted</param>
        /// <returns></returns>
        public virtual byte[] Encrypt(byte[] key, byte[] plainText)
        {
            return null;
        }


        /// <summary>
        /// Encrypt
        /// </summary>
        /// <param name="key">Encryption key</param>
        /// <param name="plainText">Plain text which are encrypted</param>
        /// <param name="unitLength"></param>
        /// <returns></returns>
        public virtual byte[] Encrypt(byte[] key, byte[] plainText, int unitLength)
        {
            return null;
        }


        /// <summary>
        /// Encrypt block of plain text
        /// </summary>
        /// <param name="key">Encryption key</param>
        /// <param name="plainTextBlock">Block of plain text to be encrypted</param>
        /// <returns></returns>
        public byte[] EncryptBlock(byte[] key, byte[] plainTextBlock)
        {
            return BlockCipher.EncryptSRS(new Block(plainTextBlock), new Block(key)).getByteArray();
        }
        #endregion


        #region Decrypt
        /// <summary>
        /// Decrypt
        /// </summary>
        /// <param name="key">Decryption key</param>
        /// <param name="cipherText">Cipher text which are decrypted</param>
        /// <returns></returns>
        public virtual byte[] Decrypt(byte[] key, byte[] cipherText)
        {
            return null;
        }


        /// <summary>
        /// Decrypt
        /// </summary>
        /// <param name="key">Decryption key</param>
        /// <param name="cipherText">Cipher text which are decrypted</param>
        /// <param name="unitLength"></param>
        /// <returns></returns>
        public virtual byte[] Decrypt(byte[] key, byte[] cipherText, int unitLength)
        {
            return null;
        }


        /// <summary>
        /// Decrypt Block.
        /// </summary>
        /// <param name="key">Decryption key</param>
        /// <param name="ci">Block of cipher to be decrypted</param>
        /// <returns></returns>
        public byte[] DecryptBlock(byte[] key, byte[] cipherTextBlock)
        {
            return BlockCipher.DecryptSRS(new Block(cipherTextBlock), new Block(key)).getByteArray();
        }
        #endregion


        #region Helper
        /// <summary>
        /// Get Block of array of byte. If Position + length exist than length of plain, add dummy elements.
        /// </summary>
        /// <param name="plain">Source</param>
        /// <param name="position">Position of Length</param>
        /// <param name="length">How many</param>
        /// <returns>Array of byte</returns>
        protected byte[] GetBlock(byte[] plain, int position, int length)
        {
            int remain = plain.Length - position;
            byte[] block = new byte[length];


            if (remain < length)
            {
                int j;

                Array.Copy(plain, position, block, 0, remain);

                // Padding, dummy elements
                for (j = remain; j < length; ++j)
                {
                    block[j] = 100;
                }
            }
            else
            {
                Array.Copy(plain, position, block, 0, length);
            }

            return block;
        }


        /// <summary>
        /// Generate intial vector. Generate using SBox.
        /// </summary>
        /// <param name="key">Key</param>
        /// <returns>Initial Vector. Length of Intial Vector equal to length of key</returns>
        protected byte[] GenerateInitialVector(byte[] key)
        {
            return key;
        }


        /// <summary>
        /// XOR operation for array of byte. Length of block1 and block2 must be same.
        /// </summary>
        /// <param name="block1"></param>
        /// <param name="block2"></param>
        /// <returns></returns>
        /// <exception cref="Length Equality Exception"></exception>
        protected byte[] XORBlock(byte[] block1, byte[] block2)
        {
            // Check Length
            if (block1.Length != block2.Length)
            {
                throw new Exception("block1's lengts must be equal to block2's length");
            }

            int i;
            int length = block1.Length;
            byte[] xorBlock = new byte[length];

            for (i = 0; i < length; ++i)
            {
                // XOR Operation
                xorBlock[i] = (byte)(block1[i] ^ block2[i]);
            }

            return xorBlock;
        }


        /// <summary>
        /// Get Lest Significant Byte
        /// </summary>
        /// <param name="arrayByte">Source</param>
        /// <param name="length">Length</param>
        /// <returns></returns>
        protected byte[] LSB(byte[] arrayByte, int length)
        {
            int position = arrayByte.Length - length;
            byte[] lsb = new byte[length];

            Array.Copy(arrayByte, position, lsb, 0, length);

            return lsb;
        }


        /// <summary>
        /// Get Most Significant Byte
        /// </summary>
        /// <param name="arrayByte">Source</param>
        /// <param name="length">Length</param>
        /// <returns></returns>
        protected byte[] MSB(byte[] arrayByte, int unitLength)
        {
            byte[] msb = new byte[unitLength];

            Array.Copy(arrayByte, 0, msb, 0, unitLength);

            return msb;
        }


        /// <summary>
        /// Generate new length. Formula : New length = ceil(plainLength / keyLength) * keyLength;
        /// </summary>
        /// <param name="plainLength">Old length</param>
        /// <param name="keyLength">Key's length</param>
        /// <returns></returns>
        protected int GenerateNewLength(int plainLength, int keyLength)
        {
            return (int)Math.Ceiling(plainLength / (float)keyLength) * keyLength;
        }


        /// <summary>
        /// Concat array
        /// </summary>
        /// <param name="arr1">First array</param>
        /// <param name="arr2">Second array</param>
        /// <returns></returns>
        public byte[] Concat(byte[] arr1, byte[] arr2)
        {
            int arr1Length = arr1.Length;
            int arr2Length = arr2.Length;
            int length = arr1Length + arr2Length;
            byte[] concat = new byte[length];

            for (int i = 0; i < arr1Length; ++i)
            {
                concat[i] = arr1[i];
            }

            for (int i = arr1Length; i < length; ++i)
            {
                concat[i] = arr2[i - arr1Length];
            }

            return concat;
        }
        #endregion
    }
}
